﻿using System.Collections.Generic;

namespace GameFramework
{
    public sealed class TaskQueue<T>
    where T : AbstractTask
    {
        private readonly LinkedList<T> m_working_list;
        private readonly LinkedList<T> m_waiting_list;

        public TaskQueue(int task_cnt)
        {
            LimitTaskCnt = task_cnt;

            m_working_list = new LinkedList<T>();
            m_waiting_list = new LinkedList<T>();
        }

        public int LimitTaskCnt { get; private set; }

        public bool Paused { get; set; }

        public int TotalTaskCount => WorkingTaskCount + WaitingTaskCount;

        public int WorkingTaskCount => m_working_list.Count;

        public int WaitingTaskCount => m_waiting_list.Count;

        public void Update()
        {
            if (Paused == true)
            {
                return;
            }

            RunningUpdate();
            WaitingUpdate();
        }

        public void Shutdown()
        {
            RemoveAll();
        }

        public void Add(T agent)
        {
            if (WorkingTaskCount < LimitTaskCnt)
            {
                agent.Start();
                m_working_list.AddLast(agent);
            }
            else
            {
                m_waiting_list.AddLast(agent);
            }
        }

        public bool Remove(int serial_id)
        {
            foreach (var task in m_waiting_list)
            {
                if (task.SerialId == serial_id)
                {
                    m_waiting_list.Remove(task);
                    ReferencePool.Push(task);
                    return true;
                }
            }

            foreach (var task in m_working_list)
            {
                if (task.SerialId == serial_id)
                {
                    m_working_list.Remove(task);
                    ReferencePool.Push(task);
                    return true;
                }
            }

            return false;
        }

        public T Get(int serial_id)
        {
            foreach (var task in m_waiting_list)
            {
                if (task.SerialId == serial_id)
                {
                    return task;
                }
            }

            foreach (var task in m_working_list)
            {
                if (task.SerialId == serial_id)
                {
                    return task;
                }
            }

            return default;
        }

        public void RemoveAll()
        {
            foreach (var task in m_waiting_list)
            {
                ReferencePool.Push(task);
            }
            m_waiting_list.Clear();

            foreach (var task in m_working_list)
            {
                ReferencePool.Push(task);
            }
            m_working_list.Clear();
        }

        public TaskInfo[] GetAllInfos()
        {
            List<TaskInfo> results = new List<TaskInfo>();
            foreach (var task in m_working_list)
            {
                results.Add(new TaskInfo(task.SerialId, task.Status, task.Description));
            }

            foreach (var task in m_waiting_list)
            {
                results.Add(new TaskInfo(task.SerialId, ETaskStatus.Todo, task.Description));
            }
            return null;
        }


        private void RunningUpdate()
        {
            var current = m_working_list.First;
            while (current != null)
            {
                var task = current.Value;
                if (task.Status == ETaskStatus.Doing)
                {
                    task.Update();
                    current = current.Next;
                    continue;
                }

                var next = current.Next;
                m_working_list.Remove(current);
                ReferencePool.Push(task);
                current = next;
            }
        }

        private void WaitingUpdate()
        {
            var current = m_waiting_list.First;
            while (current != null && WaitingTaskCount < LimitTaskCnt)
            {
                var next = current.Next;

                T task = current.Value;
                m_working_list.AddLast(task);
                task.Start();
                m_waiting_list.Remove(task);

                current = next;
            }
        }
    }
}
